#!/usr/bin/env python3
import sys
import argparse
import struct
import socket
import struct
import threading
#import psutil
import time
import inspect
from datetime import datetime
import pdb

local_ip = "127.0.0.1"
local_port =60000
# 定义报文头部结构体格式，使用网络字节序(Big Endian)
UD_PKT_HEADER_LEN = 16
UC_PKT_HEADER_FORMAT = '>B 3s I B B H I'

########################################################################################################################
RESET = "\033[0m"  # 重置所有样式
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
WHITE = "\033[37m"
REVCOL = "\033[7m"
BOLD = "\033[1m"
def DLOG(message):
    frame = inspect.stack()[1]
    filename = frame.filename
    lineno = frame.lineno
    timestamp = datetime.now().strftime('%y%m%d %H:%M:%S')
    print(f"{timestamp}|{lineno}|{message}")
    #rint(f"+++: {timestamp} ({filename}:{lineno}) {message}")

def parse_arguments():
    parser = argparse.ArgumentParser(description="cu server")
    parser.add_argument("-p", "--port", type=int, default=60000,
                        help="listen port (default: %(default)s)")
    args = parser.parse_args()
    return args.port

#def get_local_ip_address():
#    connected_nics = psutil.net_if_addrs()
#    connected_ipv4_addresses = []
#    for interface_name, interface_addresses in connected_nics.items():
#        for addr in interface_addresses:
#            if addr.family == socket.AF_INET and addr.address and addr.address != '127.0.0.1':
#                connected_ipv4_addresses.append(addr.address)
#    return connected_ipv4_addresses
#
########################################################################################################################
def parse_header(header_data):
    try: 
        ver, proto_signature, devid, pkt_type, pkt_subtype, resv1, msg_length = struct.unpack(UC_PKT_HEADER_FORMAT, header_data)
        if proto_signature != b'CUD':
            raise ValueError("Invalid proto_signature")
        resv = ver >> 4
        return {
            #'ver': ver & 0x0F,
            #'resv': resv,
            'proto_signature': proto_signature.decode(),
            'dev_id': devid,
            'pkt_type': pkt_type,
            'pkt_subtype': pkt_subtype,
            #'resv1': resv1,
            'msg_length': msg_length,
        }
    except ValueError as e:
        DLOG(f"Error parsing header: {e}")
        raise 

def handle_client(conn, addr):
    client_ip, client_port = addr[:2]
    DLOG(f"server: {local_ip}:{local_port}<--{client_ip}:{client_port}: connected")

    buffer = b''  # 初始化缓冲区
    counter = 0
    total_recv_len = 0
    conn.settimeout(5)
    while True:
        try:
            received_data = conn.recv(4096)
            if not received_data:
                DLOG(f"server: {local_ip}:{local_port}<--{client_ip}:{client_port}: client socket closed.")
                break
            counter += 1
            cur_recv_len = len(received_data)
            total_recv_len += cur_recv_len
            buffer = received_data  # 将新接收的数据添加到缓冲区
            cur_buff_len = len(buffer)
            DLOG(f"{local_ip}:{local_port}<--{client_ip}:{client_port}: "
                f"recv: ({total_recv_len}-{cur_recv_len} bytes): ....................................")
            continue

            while buffer:
                if cur_buff_len < UD_PKT_HEADER_LEN:
                    DLOG(f"server: 11111: {counter}: {len(received_data)}")
                    time.sleep(1)
                    continue
                counter += 1
                # 尝试从缓冲区中提取报文内容
                try:
                    pkt_head_data = buffer[:UD_PKT_HEADER_LEN]
                    pkt_head_info = parse_header(pkt_head_data)
                    #for key, value in pkt_head_info.items():
                    #  DLOG(f"{key}: {value}")
                    pkt_type = pkt_head_info['pkt_type']
                    pkt_subtype = pkt_head_info['pkt_subtype']
                    total_data_len = pkt_head_info['msg_length']
                    if total_data_len < UD_PKT_HEADER_LEN:
                        buffer = b''
                        DLOG(f"server: 22222: {counter}")
                        break
                    remain_data_len = total_data_len - UD_PKT_HEADER_LEN
                    pkt_body_data = buffer[UD_PKT_HEADER_LEN:UD_PKT_HEADER_LEN+remain_data_len]
                    #rint(f"++++++++++++++++++++: {total_data_len}:{remain_data_len}:{len(pkt_body_data)}")
                    if len(pkt_body_data) >= remain_data_len:
                        pkt_full_data = pkt_head_data + pkt_body_data
                        data_slice = pkt_head_data[10:14]
                        data_seqno = struct.unpack("!I", data_slice)[0]
                        DLOG(f"server:{local_port}<--{client_ip}:{client_port}: "
                            f"recv: 0x{pkt_type:02x}{pkt_subtype:02x}({total_data_len} bytes): {pkt_full_data.hex()}")
                        buffer = buffer[total_data_len:]  # 移除已提取的完整报文数据
                    else:
                        DLOG(f"server: 33333: {counter}")
                        break  # 数据不足，跳出内层循环，等待接收剩余数据
                except Exception as e:
                    DLOG(f"server: {local_ip}:{local_port}: except: parsing header or body error: {e}")
                    continue
        except socket.timeout as e:
            DLOG(f"server: {local_ip}:{local_port}<--{client_ip}:{client_port}: waiting for data......")
            continue 		
    DLOG(f"Closing connection from {addr}")
    conn.close()

def tcp_listener():
    """
    创建TCP服务器并监听指定端口，为每个连接的客户端创建独立的处理线程。
    """
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind((local_ip, local_port))
        s.listen(10) 
        s.setblocking(True)
        DLOG(f"server: XX:{local_port}: listening: ...")

        while True:
            conn, addr = s.accept()
            threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()

if __name__ == "__main__":
    if len(sys.argv) < 2:
        DLOG(f"usage: argv[0] <ip> <port>\n")
        sys.exit(1)
    DLOG(f"args={sys.argv}")
    local_ip  	= sys.argv[1]
    local_port	= int(sys.argv[2])
    tcp_listener()
